﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;

public abstract class District
{
    public enum DistrictType
    {
        Residential,    // Suburban or urban housing; Library; Museum; Primary School; Park; Grocery Store
        Services,       // Hospital; police; fire; capital; courthouse; prison (special rules)
        Utilities,      // Water; Power; Communications; Waste
        Farm,           // Farmland; Ranch; must be 6 pieces to apply
        Commercial,     // Bank; Restaurants; Big Box Stores; Business Park; Theater; Gas Station
        Industrial      // Fatory; Textile; Mining; Lumber; Warehouses; Fuel
    }

    private const int NORTH = 0;
    private const int SOUTH = 1;
    private const int EAST = 2;
    private const int WEST = 3;

    private DistrictType districtType;
    protected Dictionary<Vector2, Building.BuildingType> districtTiles = new Dictionary<Vector2, Building.BuildingType>();
    private City city;

    public DistrictType Type { get { return districtType; } }

    public District(DistrictType type, City city)
    {
        this.districtType = type;
        this.city = city;
    }

    public bool TryAddTile(Vector2 tile)
    {
        List<Vector2> adjacentTiles = AdjacentTiles(tile);

        if (districtTiles.ContainsKey(adjacentTiles[NORTH]) ||
            districtTiles.ContainsKey(adjacentTiles[SOUTH]) ||
            districtTiles.ContainsKey(adjacentTiles[EAST]) ||
            districtTiles.ContainsKey(adjacentTiles[WEST]))
        {
            districtTiles.Add(tile, city.BuildingsByTile[tile].type);
            return true;
        }

        return false;
    }

    /// <summary>
    /// Use this method to determine if a district is still whole after a piece is removed.
    /// </summary>
    public bool IsValidAfterRemoval(List<Vector2> tilesRemoved)
    {
        foreach (Vector2 tileRemoved in tilesRemoved)
        {
            if (!districtTiles.ContainsKey(tileRemoved)) return false;
        }

        foreach (Vector2 tileRemoved in tilesRemoved)
        {
            districtTiles.Remove(tileRemoved);
        }

        // Now check for interconnectedness between all tiles.
        // A little inefficient but our data is small enough that it shouldn't be noticeable.
        foreach (Vector2 districtTile in districtTiles.Keys)
        {
            List<Vector2> adjacentTiles = AdjacentTiles(districtTile);

            if (!districtTiles.ContainsKey(adjacentTiles[NORTH]) &&
                !districtTiles.ContainsKey(adjacentTiles[SOUTH]) &&
                !districtTiles.ContainsKey(adjacentTiles[EAST]) &&
                !districtTiles.ContainsKey(adjacentTiles[WEST]))
            {
                return false;
            }
        }

        return true;
    }

    internal void ForceAddTile(Vector2 location)
    {
        if (!districtTiles.ContainsKey(location)) districtTiles.Add(location, city.BuildingsByTile[location].type);
    }

    public bool IsDistrictComplete(City city, UnityEngine.Vector2 startingLocation)
    {
        // 1. Make sure the building type at this location belongs in this district; 
        // throw an exception otherwise.
        if (!IsValidBuildingType(city.BuildingsByTile[startingLocation].type))
            throw new ArgumentException("Invalid building type " + city.BuildingsByTile[startingLocation].type + " used as starting location for residential district.");

        // 2. Gather all interconnected valid building types.
        ExtendDistrictFromTile(city, startingLocation);

        // 3. Test validity.
        return IsCompleteDistrict(GetTypesInDistrict());
    }

    protected abstract bool IsCompleteDistrict(Dictionary<Building.BuildingType, int> typesInDistrict);

    protected abstract bool IsValidBuildingType(Building.BuildingType buildingType);

    protected abstract float GetDistrictScore(List<Building> buildings);

    public float GetDistrictScore()
    {
        // Get all buildings in this district
        List<Building> buildings = new List<Building>();

        foreach (Vector2 tile in districtTiles.Keys)
        {
            Building building = city.BuildingsByTile[tile];
            if (!buildings.Contains(building)) buildings.Add(building);
        }

        return GetDistrictScore(buildings);

    }

    /// <summary>
    /// Recursive method to collect all valid interconnected tiles.
    /// </summary>
    /// <param name="startingLocation">The location to continue looking from.</param>
    protected void ExtendDistrictFromTile(City city, Vector2 startingLocation)
    {
        districtTiles.Add(startingLocation, city.BuildingsByTile[startingLocation].type);
        List<Vector2> adjacentTiles = AdjacentTiles(startingLocation);
        foreach (Vector2 adjacentTile in adjacentTiles)
        {
            if (!districtTiles.ContainsKey(adjacentTile))
            {
                if (city.BuildingsByTile.ContainsKey(adjacentTile) && IsValidBuildingType(city.BuildingsByTile[adjacentTile].type))
                {
                    ExtendDistrictFromTile(city, adjacentTile);
                }
            }
        }
    }

    private List<Vector2> AdjacentTiles(Vector2 tile)
    {
        List<Vector2> adjacentTiles = new List<Vector2>();
        
        Vector2 north = tile + new Vector2(0, 1);
        Vector2 south = tile + new Vector2(0, -1);
        Vector2 east = tile + new Vector2(1, 0);
        Vector2 west = tile + new Vector2(-1, 0);

        adjacentTiles.Add(north);
        adjacentTiles.Add(south);
        adjacentTiles.Add(east);
        adjacentTiles.Add(west);

        return adjacentTiles;
    }



    private Dictionary<Building.BuildingType, int> GetTypesInDistrict()
    {
        Dictionary<Building.BuildingType, int> typesInDistrict = new Dictionary<Building.BuildingType, int>();

        List<Building> buildingsInDistrict = new List<Building>();

        foreach (Vector2 tile in districtTiles.Keys)
        {
            if (!buildingsInDistrict.Contains(city.BuildingsByTile[tile]))
            {
                buildingsInDistrict.Add(city.BuildingsByTile[tile]);
            }
        }

        foreach (Building building in buildingsInDistrict) 
        {
            if (!typesInDistrict.ContainsKey(building.type)) typesInDistrict.Add(building.type, 0);

            typesInDistrict[building.type]++;
        }

        return typesInDistrict;

    }

    internal static District CreateFromTile(City city, Vector2 tile)
    {
        // An ugly-looking hack that isn't actually that bad. We're under a time crunch. -Steve

        CommercialDistrict commercial = new CommercialDistrict(city);
        FarmDistrict farm = new FarmDistrict(city);
        IndustrialDistrict industrial = new IndustrialDistrict(city);
        ResidentialDistrict residential = new ResidentialDistrict(city);
        ServicesGovernmentDistrict services = new ServicesGovernmentDistrict(city);
        UtilitiesDistrict utilities = new UtilitiesDistrict(city);

        Building.BuildingType buildingType = city.BuildingsByTile[tile].type;

        if (commercial.IsValidBuildingType(buildingType) && commercial.IsDistrictComplete(city, tile)) return commercial;
        if (farm.IsValidBuildingType(buildingType) && farm.IsDistrictComplete(city, tile)) return farm;
        if (industrial.IsValidBuildingType(buildingType) && industrial.IsDistrictComplete(city, tile)) return industrial;
        if (residential.IsValidBuildingType(buildingType) && residential.IsDistrictComplete(city, tile)) return residential;
        if (services.IsValidBuildingType(buildingType) && services.IsDistrictComplete(city, tile)) return services;
        if (utilities.IsValidBuildingType(buildingType) && utilities.IsDistrictComplete(city, tile)) return utilities;

        return null;

    }


}
